package net.sf.calibur.pool;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.softee.management.annotation.MBean;
import org.softee.management.annotation.ManagedAttribute;

import net.sf.calibur.jmx.JmxRegister;
import net.sf.calibur.jmx.ManagedBean;

@MBean
public class KeyedObjectPool implements Runnable, ManagedBean {
	private final static int MAX_SLEEPING = 100;
	private final static long MAX_BORROWED_TIME = 10000L;

	private static KeyedObjectPool instance = null;

	public static synchronized KeyedObjectPool init() {
		if (instance != null) {
			throw new IllegalStateException(
					"KeyedObjectPool has already initialized by maxSleeping="
							+ instance.maxSleeping + " maxBorrowedTime="
							+ instance.maxBorrowedTime);
		}

		instance = new KeyedObjectPool(MAX_SLEEPING, MAX_BORROWED_TIME);
		return instance;
	}

	public static synchronized KeyedObjectPool init(int maxSleeping,
			long maxBorrowedTime) {
		if (instance != null) {
			throw new IllegalStateException(
					"KeyedObjectPool has already initialized by maxSleeping="
							+ instance.maxSleeping + " maxBorrowedTime="
							+ instance.maxBorrowedTime);
		}

		instance = new KeyedObjectPool(maxSleeping, maxBorrowedTime);
		return instance;
	}

	public static KeyedObjectPool getInstance() {
		if (instance == null) {
			throw new IllegalStateException(
					"KeyedObjectPool has not been initialized, "
							+ "please invoke KeyedObjectPool#init to initialize it.");
		}

		return instance;
	}

	// //////////////////////////////////////////////////////////////////////////////
	private Map<Object, ObjectPool> pool;
	private long maxBorrowedTime;
	private int maxSleeping;
	private List<String> stackElementFilters;
	private Logger logger = LoggerFactory.getLogger(getClass());

	private KeyedObjectPool(int maxSleeping, long maxBorrowedTime) {
		this.maxSleeping = maxSleeping;
		this.maxBorrowedTime = maxBorrowedTime;

		pool = new ConcurrentHashMap<Object, ObjectPool>();
		stackElementFilters = new ArrayList<String>();
	}
	
	public void addStackElementFilter(String s) {
		stackElementFilters.add(s);
	}

	@Override
	public String getObjectName() {
		return "calibur.objectPool:name=pool";
	}

	@Override
	public void run() {
		List<ActiveObjectTrace> list = new ArrayList<ActiveObjectTrace>();
		while (true) {
			try {
				Thread.sleep(MAX_BORROWED_TIME);
			} catch (Exception e) {
				logger.error("KeyedObjectPool monitor thread exception.", e);
				return;
			}

			list.clear();
			for (ObjectPool op : pool.values()) {
				op.getActiveObjectTrace(list);
			}

			for (ActiveObjectTrace rec : list) {
				if (rec.getHoldTime() > maxBorrowedTime) {
					logger.warn("KeyedObjectPool borrowed object time too long. "
							+ rec.toString());
				}
			}
		}
	}

	public void addObjectFactory(PooledObjectFactory objectFactory) {
		if (objectFactory == null) {
			throw new IllegalArgumentException(
					"PooledObjectFactory cannot be null.");
		}

		Object key = objectFactory.getKey();
		if (key == null) {
			throw new IllegalArgumentException(
					"key of PooledObjectFactory cannot be null.");
		}

		if (pool.containsKey(key)) {
			logger.warn("Same key's PooledObjectFactory be overrided, key="
					+ key);
		}

		ObjectPool specPool = new ObjectPool(objectFactory, maxSleeping);
		pool.put(key, specPool);
		// 注册相应的统计MBean
		PooledObjectStatistics statsMBean = new PooledObjectStatistics(specPool);
		JmxRegister.register(statsMBean);
	}

	public Object borrowObject(Object key) throws Exception {
		ObjectPool specPool = pool.get(key);
		if (specPool == null) {
			throw new IllegalArgumentException("not existed pool key : " + key);
		}

		return specPool.borrowObject();
	}

	@SuppressWarnings("unchecked")
	public <T> T borrowObjectByClass(Class<T> clazz) throws Exception {
		return (T) borrowObject(clazz);
	}

	public void returnObject(Object key, Object object) throws Exception {
		ObjectPool specPool = pool.get(key);
		if (specPool == null) {
			throw new IllegalArgumentException("not existed pool key : " + key);
		}

		specPool.returnObject(object);
	}

	public void returnObjectByClass(Object object) throws Exception {
		returnObject(object.getClass(), object);
	}

	@ManagedAttribute
	public Collection<String> getActiveObjectsStatistics() {
		List<ActiveObjectTrace> list = new ArrayList<ActiveObjectTrace>();
		for (ObjectPool op : pool.values()) {
			op.getActiveObjectTrace(list);
		}
		Collections.sort(list, new Comparator<ActiveObjectTrace>() {
			@Override
			public int compare(ActiveObjectTrace o1, ActiveObjectTrace o2) {
				return (int) (o2.getHoldTime() - o1.getHoldTime());
			}
		});

		List<String> stats = new ArrayList<String>();
		for (ActiveObjectTrace trace : list) {
			stats.add(trace.toString(stackElementFilters));
		}

		return stats;
	}

	@ManagedAttribute
	public int getMaxSleeping() {
		return maxSleeping;
	}

	@ManagedAttribute
	public int getNumActive() {
		int sum = 0;
		for (ObjectPool op : pool.values()) {
			sum += op.getNumActive();
		}

		return sum;
	}

	@ManagedAttribute
	public int getNumIdle() {
		int sum = 0;
		for (ObjectPool op : pool.values()) {
			sum += op.getNumIdle();
		}

		return sum;
	}

	@ManagedAttribute
	public long getMaxBorrowedTime() {
		return maxBorrowedTime;
	}
}